package org.odk.aggregate.personfindermobile;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.google.appengine.api.datastore.Entity;

public class RecordSubmitter {
  
  private static final Logger logger = Logger.getLogger(RecordSubmitter.class.getCanonicalName());
  
  // TODO(mimi): change this to "false" to enable submission
  static boolean DRY_RUN = true;

  private static final String APP_DOMAIN = "person-finder-mobile.appspot.com/";

  private static final String[] PERSON_ATTRS = {"entry_date", "author_name", "author_email", "author_phone", "source_name", "source_date", "source_url", 
      "first_name", "last_name", "home_city", "home_state", "home_neighborhood", "home_street", "home_zip", "photo_url", "other"};

  private static final String[] NOTE_ATTRS = {"entry_date", "author_name", "author_email", "author_phone", "source_date", "found", 
      "email_of_found_person", "phone_of_found_person", "last_known_location", "text"};

  private static final String PERSON_FINDER_KEY = "IIYbllzTulWIPB0N";
  private static final String PF_URL = "http://haiticrisis.appspot.com/api/write?key=" + PERSON_FINDER_KEY;
  
  private static final Logger LOGGER = Logger.getLogger(RecordSubmitter.class.getName());
  
  /**
   * Submits a person record to Person Finder in the PFIF format. 
   * @return 
   */
  public String submit(Entity entity) {
    try {
      String pfif = generatePFIF(entity);
      return "pfif: " + pfif + " postResult: " + postPFIF(pfif);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  
  private String postPFIF(String pfif) throws IOException {
    logger.info("Submitting the following pfif to Person Finder: " + pfif);
    if (DRY_RUN) return "DRY_RUN, nothing submitted";
    
    URL url = new URL(PF_URL);
    
    HttpURLConnection c = (HttpURLConnection) url.openConnection();
    c.setRequestMethod("POST");
    c.setRequestProperty("Content-Type", "text/xml; charset=\"utf-8\"");
    c.setDoOutput(true);
    c.setDoInput(true);
    
    OutputStreamWriter out = new OutputStreamWriter(c.getOutputStream(), "UTF8");
    out.write(pfif);
    out.flush();
    
    // Get the response
    BufferedReader rd = new BufferedReader(new InputStreamReader(c.getInputStream()));
    String line;
    StringBuilder response = new StringBuilder();
    while ((line = rd.readLine()) != null) {
      response.append(line);
    }
    logger.info("Received the following response from Person Finder: " + response.toString());
    out.close();
    rd.close();
    return response.toString();
  }
  
  private String generatePFIF(Entity entity) throws TransformerException, ParserConfigurationException {
    String pfif = null;
    OutputStream outStream = new ByteArrayOutputStream();
 
    // create XML DOM document
    DOMImplementation impl = DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation();
    Document xmldoc = impl.createDocument("http://zesty.ca/pfif/1.1", "pfif:pfif", null);
    xmldoc.setXmlVersion("1.0");
    Element root = xmldoc.getDocumentElement();
    Element person = xmldoc.createElement("pfif:person");
    root.appendChild(person);
    
    // add basic person attributes
    person.appendChild(getElement(xmldoc, "person_record_id", APP_DOMAIN + entity.getKey().toString()));
    addAttrs(xmldoc, entity, person, PERSON_ATTRS);

    // add note
    Element note = xmldoc.createElement("pfif:note");
    person.appendChild(note);
    note.appendChild(getElement(xmldoc, "note_record_id", APP_DOMAIN + entity.getKey().toString()));
    addAttrs(xmldoc, entity, note, NOTE_ATTRS);


    // serialisation through Tranform.
    DOMSource domSource = new DOMSource(xmldoc);
    StreamResult streamResult = new StreamResult(outStream);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer serializer = tf.newTransformer();
    serializer.setOutputProperty(OutputKeys.INDENT, "yes");
    serializer.transform(domSource, streamResult);
    
    pfif = new String(((ByteArrayOutputStream)streamResult.getOutputStream()).toByteArray());
    
    LOGGER.info(pfif);

    return pfif;
  }
  
  private void addAttrs(Document xmldoc, Entity entity, Element base, String[] attrs) {
    for (String attr : attrs) {
      String value = null;
      String[] variants = {attr, attr + "string", "note_" + attr + "string"};
      for (String variant : variants) {
        if (entity.getProperty(variant) != null) {
          value = entity.getProperty(variant).toString();
        }
      }
      
      if (value != null) {
        base.appendChild(getElement(xmldoc, attr, value));
      }
    }

  }

  private Element getElement(Document xmldoc, String name, String value) {
    Element e = xmldoc.createElement("pfif:" + name);
    Node n = xmldoc.createTextNode(value);
    e.appendChild(n);
    return e;
  }
}
